package com.hdu.metrics_center.core;

import lombok.val;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.WildcardQueryBuilder;
import org.elasticsearch.script.Script;

import java.util.Objects;

import static com.hdu.metrics_center.core.FilterType.*;
import static java.lang.String.format;
import static java.util.Optional.ofNullable;
import static org.elasticsearch.index.query.QueryBuilders.*;


/**
 * 提供 boolQueryFilter -> QueryBuilder 功能
 */

public class EsQueryBuilderBuildHelper {

    private static final String LIKE_QUERY_TEMPLATE = "*%s*";
    private static final String LIKE_LEFT_QUERY_TEMPLATE = "*%s";
    private static final String LIKE_RIGHT_QUERY_TEMPLATE = "%s*";

    public static QueryBuilder convertToQueryBuilder(QueryFilter queryFilter) {

        if (queryFilter == null) {
            return null;
        }

        val boolQuery = QueryBuilders.boolQuery();
        val type = queryFilter.getType();
        switch (type) {
            case NESTED:
                buildNestedQuery(queryFilter, boolQuery);
                return boolQuery;
            case EQ:
                return termQuery(queryFilter.getField(), queryFilter.getEq());
            case NOT_EQ:
                return boolQuery.mustNot(termQuery(queryFilter.getField(), queryFilter.getNotEq()));
            case RANGE:
                return buildRangeQuery(queryFilter);
            case NOT_RANGE:
                return boolQuery.mustNot(buildRangeQuery(queryFilter));
            case IN:
                return termsQuery(queryFilter.getField(), queryFilter.getIn());
            case NOT_IN:
                return boolQuery.mustNot(termsQuery(queryFilter.getField(), queryFilter.getNotIn()));
            case EXISTS:
                return existsQuery(queryFilter.getField());
            case NOT_EXISTS:
                return boolQuery.mustNot(existsQuery(queryFilter.getField()));
            case EXP:
                return scriptQuery(new Script(queryFilter.getExp()));
            case LIKE:
                return buildWildcardQuery(
                        queryFilter.getField(),
                        format(LIKE_QUERY_TEMPLATE, queryFilter.getLike()),
                        LIKE
                );
            case LIKE_LEFT:
                return buildWildcardQuery(
                        queryFilter.getField(),
                        format(LIKE_LEFT_QUERY_TEMPLATE, queryFilter.getLikeLeft()),
                        LIKE_LEFT
                );
            case LIKE_RIGHT:
                return buildWildcardQuery(
                        queryFilter.getField(),
                        format(LIKE_RIGHT_QUERY_TEMPLATE, queryFilter.getLikeRight()),
                        LIKE_RIGHT
                );
            case NOT_LIKE:
                return buildNotWildcardQuery(
                        queryFilter.getField(),
                        format(LIKE_QUERY_TEMPLATE, queryFilter.getNotLike()),
                        LIKE
                );
            case NOT_LIKE_LEFT:
                return buildNotWildcardQuery(
                        queryFilter.getField(),
                        format(LIKE_LEFT_QUERY_TEMPLATE, queryFilter.getNotLikeLeft()),
                        LIKE_LEFT
                );
            case NOT_LIKE_RIGHT:
                return buildNotWildcardQuery(
                        queryFilter.getField(),
                        format(LIKE_RIGHT_QUERY_TEMPLATE, queryFilter.getNotLikeRight()),
                        LIKE_RIGHT
                );
            default:
                throw new UnsupportedOperationException("Unsupported FilterType: " + type);
        }
    }

    private static void buildNestedQuery(QueryFilter queryFilter, BoolQueryBuilder boolQuery) {
        val mustList = queryFilter.getMust();
        if (mustList != null && !mustList.isEmpty()) {
            mustList.stream()
                    .map(EsQueryBuilderBuildHelper::convertToQueryBuilder)
                    .filter(Objects::nonNull)
                    .forEach(boolQuery::must);
        }

        val mustNotList = queryFilter.getMustNot();
        if (mustNotList != null && !mustNotList.isEmpty()) {
            mustNotList.stream()
                    .map(EsQueryBuilderBuildHelper::convertToQueryBuilder)
                    .filter(Objects::nonNull)
                    .forEach(boolQuery::mustNot);
        }

        val shouldList = queryFilter.getShould();
        if (shouldList != null && !shouldList.isEmpty()) {
            shouldList.stream()
                    .map(EsQueryBuilderBuildHelper::convertToQueryBuilder)
                    .filter(Objects::nonNull)
                    .forEach(boolQuery::should);
            boolQuery.minimumShouldMatch(1);
        }
    }

    private static QueryBuilder buildRangeQuery(QueryFilter queryFilter) {
        val rangeQueryBuilder = rangeQuery(queryFilter.getField());
        ofNullable(queryFilter.getGt()).ifPresent(rangeQueryBuilder::gt);
        ofNullable(queryFilter.getGte()).ifPresent(rangeQueryBuilder::gte);
        ofNullable(queryFilter.getLt()).ifPresent(rangeQueryBuilder::lt);
        ofNullable(queryFilter.getLte()).ifPresent(rangeQueryBuilder::lte);
        return rangeQueryBuilder;
    }

    private static QueryBuilder buildWildcardQuery(String filed, Object v, FilterType type) {
        String template;
        switch (type) {
            case LIKE:
                template = LIKE_QUERY_TEMPLATE;
                break;
            case LIKE_LEFT:
                template = LIKE_LEFT_QUERY_TEMPLATE;
                break;
            case LIKE_RIGHT:
                template = LIKE_RIGHT_QUERY_TEMPLATE;
                break;
            default:
                throw new UnsupportedOperationException("Unsupported FilterType: " + type);
        }
        return new WildcardQueryBuilder(
                filed,
                format(template, v)
        );
    }

    private static BoolQueryBuilder buildNotWildcardQuery(String filed, Object v, FilterType type) {
        return boolQuery().mustNot(
                buildWildcardQuery(filed, v, type)
        );
    }
}
